#include "fxcc/core/graph/opengl3/TextureCubeJack.h"
#include "fxcc/core/graph/opengl3/Shader.h"
#include "fxcc/core/graph/opengl3/MeshJack.h"
#include "fxcc/core/graph/opengl3/RenderTargetJack.h"
#include "fxcc/core/graph/opengl3/RenderBuffer.h"
#include "fxcc/core/graph/opengl3/Texture2DJack.h"
#include "fxcc/core/graph/opengl3/Effect3d.h"
#include "fxcc/core/graph/opengl3/RenderStates.h"
#include "fxcc/core/graph/common/gliUtils.h"

#include "fxcc/core/cmdUtils.h"

#include "sysx/System.h"

#include "stb_image_write.h"


fxcc::graph::opengl3::TextureCubeJack::TextureCubeJack()
    :fxcc::graph::opengl3::Texture(GL_TEXTURE_CUBE_MAP)
{
}

fxcc::graph::opengl3::TextureCubeJack::TextureCubeJack(const Desc& desc)
    :fxcc::graph::opengl3::Texture(GL_TEXTURE_CUBE_MAP), fxcc::graph::common::TextureCubeJack(desc)
{
    Load();
}


bool fxcc::graph::opengl3::TextureCubeJack::Init()
{
    switch (m_Desc.m_Type)
    {
    case Desc::Type::_DDS_:
        return InitDDS();
    case Desc::Type::_WIC_:
        return InitImage6();
    case Desc::Type::_HDR_:
        return InitHDR();
    case Desc::Type::_ALLOC_:
        return InitAlloc();
    default:
        return false;
    }
}

bool fxcc::graph::opengl3::TextureCubeJack::InitDDS()
{
    m_Id = gliUtils2::create_texture(m_Desc.m_DDSPath.c_str());
    if (m_Id)
    {
        Bind();
        SetSampler(m_Desc.m_SamplerType);
        UnBind();

        // Get data
        gli::texture texture = gli::load(m_Desc.m_DDSPath);
        auto Extent0 = texture.extent(0);
        auto fmt = texture.format();

        m_Desc.m_HDRConfig.m_Width = Extent0.x;
        m_Desc.m_HDRConfig.m_Height = Extent0.y;
        m_Desc.m_HDRConfig.m_NrComponent = gli::component_count(fmt);
        m_Desc.m_HDRConfig.m_HDR = gli::is_float(fmt);
    }

    return true;
}
bool fxcc::graph::opengl3::TextureCubeJack::InitImage6()

{
    Create();
    Bind();

    for (int i = 0; i < 6; i++)
    {
        fxcc::graph::common::ImageMipmap::Desc desc;
        desc.m_wicPath = m_Desc.m_WICImages[i];
        desc.m_wicReq = m_Desc.m_HDRConfig.m_NrComponent;
        desc.m_wicHdr = m_Desc.m_HDRConfig.m_HDR;

        fxcc::graph::common::ImageMipmap image(desc);

        TexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, image.m_WICTexture);

    }
    SetSampler(m_Desc.m_SamplerType);
    UnBind();
    return true;
}

void fxcc::graph::opengl3::TextureCubeJack::Load()
{
    m_Available = Init();
}

bool fxcc::graph::opengl3::TextureCubeJack::InitHDR()
{
    auto& rs = fxcc::graph::common::RenderStates::Inst();

    if (!fs::exists(m_Desc.m_HDRPath))
    {
        ztclog::info("failed find the hdr file %s", m_Desc.m_HDRPath.c_str());
        return false;
    }
    ztclog::info("[cube] init cube textrue from hdr %s", m_Desc.m_HDRPath.c_str());

    fxcc::graph::common::Texture2DJack::Desc hdrSourceDesc;
    bool flag = hdrSourceDesc.SetWIC(m_Desc.m_HDRPath, 4, 2, true);

    if (!flag)
    {
        ztclog::info("failed find hdr source");
        return false;
    }
    hdrSourceDesc.m_SampleType = fxcc::graph::common::SamplerType::_sampler_2d_default_;
    auto hdrTexture2D = std::make_shared<fxcc::graph::opengl3::Texture2DJack>(hdrSourceDesc);

    InitAlloc();
    Bind();

    auto equirectangularToCubemapEffect = std::make_shared<fxcc::graph::opengl3::Effect3d>(EquirectangluarToCubeMapFragCode());
    equirectangularToCubemapEffect->m_PipeLineType.m_RasterizerType = fxcc::graph::common::RasterizerType::_NONE_;

    auto mesh = std::make_shared<fxcc::graph::opengl3::MeshJack>(fxcc::graph::common::Geometry::CreateBox());
    auto m_FrameBuffer = std::make_shared<fxcc::graph::opengl3::FrameBuffer>();
    auto m_RenderBuffer = std::make_shared < fxcc::graph::opengl3::RenderBuffer>();

    m_FrameBuffer->Bind();
    m_RenderBuffer->Bind();
    m_RenderBuffer->Storage(GL_DEPTH_COMPONENT24, m_Desc.m_HDRConfig.m_Width, m_Desc.m_HDRConfig.m_Height);
    m_FrameBuffer->FrameRenderBuffer(GL_DEPTH_ATTACHMENT, m_RenderBuffer->m_Id);

    equirectangularToCubemapEffect->Begin();
    equirectangularToCubemapEffect->m_Shader->Use();
    equirectangularToCubemapEffect->m_Shader->setInt("equirectangularMap", 0);
    hdrTexture2D->Active(0);

    Effect3d::PassData passData;
    Effect3d::ObjData objData;

    passData.m_Proj = rs.captureProjection;

    for (unsigned int i = 0; i < 6; ++i)
    {
        m_FrameBuffer->Bind();

        glViewport(0, 0, m_Desc.m_HDRConfig.m_Width, m_Desc.m_HDRConfig.m_Height);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, m_Id, 0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        passData.m_View = rs.captureViews[i];
        passData.m_ProjView = passData.m_Proj * passData.m_View;

        equirectangularToCubemapEffect->SetPassData(passData);

        fxcc::graph::common::Transform transform;
        objData.Load(transform);
        equirectangularToCubemapEffect->SetObjData(objData);

        equirectangularToCubemapEffect->SetFixed();
        mesh->Bind();
        mesh->DrawElementsAuto();
    }

    fxcc::graph::opengl3::FrameBuffer::UnBind();
    fxcc::graph::opengl3::RenderBuffer::UnBind();
    fxcc::graph::opengl3::TextureCubeJack::UnBind();

   

    return true;
}

bool fxcc::graph::opengl3::TextureCubeJack::InitAlloc()
{
    Create();
    Bind();
    for (int i = 0; i < 6; i++)
    {
        TexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, m_Desc.m_HDRConfig);
    }

    SetSampler(m_Desc.m_SamplerType);

    UnBind();

    return true;
}


std::string fxcc::graph::opengl3::TextureCubeJack::EquirectangluarToCubeMapFragCode()
{
    return R"(

out vec4 FragColor;

uniform sampler2D equirectangularMap;

const vec2 invAtan = vec2(0.1591, 0.3183);
vec2 SampleSphericalMap(vec3 v)
{
    vec2 uv = vec2(atan(v.z, v.x), asin(v.y));
    uv *= invAtan;
    uv += 0.5;
    return uv;
}

void main()
{		
    vec3 localPos=vOut.NativePos;

    vec2 uv = SampleSphericalMap(normalize(localPos));
    vec3 color = texture(equirectangularMap, uv).rgb;
    
    FragColor = vec4(color, 1.0);
}

)";
};



std::string fxcc::graph::opengl3::TextureCubeJack::GetFacePath(const std::string& must, int index)
{
    auto& rs = fxcc::graph::common::RenderStates::Inst();

    kainjow::mustache::mustache m1 = must;
    kainjow::mustache::data d1;
    d1.set("index", std::to_string(index));
    d1.set("faceName", rs.CubeFaceName[index]);

    return m1.render(d1);
}

bool fxcc::graph::opengl3::TextureCubeJack::CreateFaceImages(const std::string& pathMustache, bool gamma)
{
    auto& rs = fxcc::graph::opengl3::RenderStates::Inst();

    const auto& m_HDRConfig = m_Desc.m_HDRConfig;

    const int width = m_HDRConfig.m_Width;
    const int height = m_HDRConfig.m_Height;
    const int nrComponent = m_HDRConfig.m_NrComponent;
    const int hdr = m_HDRConfig.m_HDR;

    GenerateFaceTexture2D(gamma);
    for (unsigned int i = 0; i < 6; ++i)
    {
        std::string faceFilePath = GetFacePath(pathMustache, i);
        m_FaceTextures[i]->SaveImage(faceFilePath);
    }
    return true;
};

bool fxcc::graph::opengl3::TextureCubeJack::CreateCubeDDS(const std::string& ddsPath)
{
    GenerateFaceTexture2D();
    
    std::vector<std::string> facePaths;

    for (int i = 0; i < 6; i++)
    {
        std::string facePathNosuffix = std::to_string(i);
        m_FaceTextures[i]->SaveImage(facePathNosuffix);

        std::string realPath = facePathNosuffix + m_FaceTextures[i]->GetImageSuffix();
        facePaths.push_back(realPath);
    }

    glm::ivec2 ddsSize;
    ddsSize.x = m_Desc.m_HDRConfig.m_Width;
    ddsSize.y = m_Desc.m_HDRConfig.m_Height;
    cmdUtils::CreateCubeDDSFromImage(facePaths, ddsPath, ddsSize);

    
    for (int i = 0; i < 6; i++) {
        std::string facePathNosuffix = std::to_string(i);
        std::string realPath = facePathNosuffix + m_FaceTextures[i]->GetImageSuffix();
        if (fs::exists(realPath))
        {
            fs::remove(realPath);
        };
    };

    return true;
};

bool fxcc::graph::opengl3::TextureCubeJack::CreateCubeDDSMipmap(const std::string& ddsPath)
{
    assert(m_Desc.m_HDRConfig.m_Mipmaps);

    auto glifmt = gliUtils::GetFormat(m_Desc.m_HDRConfig.m_NrComponent, m_Desc.m_HDRConfig.m_HDR);

    gli::texture_cube texture(glifmt, gli::extent2d(m_Desc.m_HDRConfig.m_Width, m_Desc.m_HDRConfig.m_Height), m_Desc.m_HDRConfig.m_Mipmaps);

    for (int level = 0; level < m_Desc.m_HDRConfig.m_Mipmaps; level++)
    {
        for (int face = 0; face < 6; face++)
        {
            auto image = this->GetImage2D(GetFaceTarget(face), level, m_Desc.m_HDRConfig.m_NrComponent, m_Desc.m_HDRConfig.m_HDR);

            gli::extent2d extent = texture.extent(level);

            std::size_t expectedSize = texture.size(level);
            std::size_t imageDataSize = image.DataSize();
            assert(imageDataSize == expectedSize);
            memcpy(texture.data(0, face, level), image.GetData(), imageDataSize);
        }
    }

    fxcc::FileUtil::CreateParentDirs(ddsPath);
    gli::save_dds(texture, ddsPath);
    return fs::exists(ddsPath);
};

int fxcc::graph::opengl3::TextureCubeJack::GetFaceTarget(int index)
{
    return GL_TEXTURE_CUBE_MAP_POSITIVE_X + index;
}
;

bool fxcc::graph::opengl3::TextureCubeJack::GenerateFaceTexture2D(bool gamma)
{
    auto& rs = fxcc::graph::common::RenderStates::Inst();

    std::shared_ptr<fxcc::graph::opengl3::RenderTargetJack> m_FaceRT[6];
    std::shared_ptr<fxcc::graph::opengl3::Effect3d> m_CubeFaceEffect;
    std::shared_ptr<fxcc::graph::opengl3::MeshJack> m_CubeMesh;

    m_CubeMesh = std::make_shared<fxcc::graph::opengl3::MeshJack>(fxcc::graph::common::Geometry::CreateBox());

    m_CubeFaceEffect = std::make_shared<fxcc::graph::opengl3::Effect3d>(cubeFaceFragCode());
    m_CubeFaceEffect->m_PipeLineType.m_RasterizerType = fxcc::graph::common::RasterizerType::_NONE_;

    for (int i = 0; i < 6; i++)
    {
        auto hdrConfig = m_Desc.m_HDRConfig;
        if (m_Desc.m_Type == fxcc::graph::common::TextureCubeJack::Desc::_WIC_)
        {
            hdrConfig.m_HDR = 0;
        }
        fxcc::graph::common::RenderTargetJack::Desc desc(1, { hdrConfig });
        //desc.m_AttachmentType = fxcc::graph::common::AttachmentType::_None_;

        m_FaceRT[i] = std::make_shared<fxcc::graph::opengl3::RenderTargetJack>(desc);
        m_FaceTextures[i] = m_FaceRT[i]->m_Textures[0];
    }

    Effect3d::PassData passData;
    Effect3d::ObjData objData;

    for (int i = 0; i < 6; i++)
    {
        auto rt = m_FaceRT[i];
        rt->Begin();
        glClearColor(1.f, 1.0f, 1.0f, 1.f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        m_CubeFaceEffect->Begin();
        //m_CubeFaceEffect->SetCamera(camera);


        passData.m_Proj = rs.captureProjection;
        passData.m_View = rs.captureViews[i];
        passData.m_ProjView = passData.m_Proj * passData.m_View;

        m_CubeFaceEffect->m_Shader->Use();
        m_CubeFaceEffect->m_Shader->setInt("cubeMap", 0);
        m_CubeFaceEffect->m_Shader->setInt("gamma", gamma);
        Active(0);

        m_CubeFaceEffect->SetPassData(passData);

        fxcc::graph::common::Transform transform;
        objData.Load(transform);
        m_CubeFaceEffect->SetObjData(objData);
        m_CubeFaceEffect->InstMeshJack(m_CubeMesh.get());
        m_CubeFaceEffect->End();

        rt->End();
    }
    return true;
};


const std::string fxcc::graph::opengl3::TextureCubeJack::cubeFaceFragCode()
{
    return  R"(
out vec4 FragColor;

uniform samplerCube cubeMap;
uniform int gamma;

void main()
{
    vec3 localPos=vOut.NativePos;

    vec3 envColor = texture(cubeMap, localPos).xyz;
    // HDR tonemap and gamma correct

    if(gamma!=0)
    {
        envColor = pow(envColor, vec3(1.0/2.2)); 
    }

    
    FragColor = vec4(envColor, 1.0);
}

)";
}
;

